// Copyright 2009 John Diss (www.newgrove.com)
/*
 * This license governs use of the accompanying software. If you use the software, you
 * accept this license. If you do not accept the license, do not use the software.
 * 
 * 1. Definitions
 * The terms "reproduce," "reproduction," "derivative works," and "distribution" have the
 * same meaning here as under U.S. copyright law.
 * A "contribution" is the original software, or any additions or changes to the software.
 * A "contributor" is any person that distributes its contribution under this license.
 * "Licensed patents" are a contributor's patent claims that read directly on its contribution.
 * 
 * 2. Grant of Rights
 * (A) Copyright Grant- Subject to the terms of this license, including the license conditions 
 *     and limitations in section 3, each contributor grants you a non-exclusive, worldwide, 
 *     royalty-free copyright license to reproduce its contribution, prepare derivative works 
 *     of its contribution, and distribute its contribution or any derivative works that you create.
 * (B) Patent Grant- Subject to the terms of this license, including the license conditions and 
 *     limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free 
 *     license under its licensed patents to make, have made, use, sell, offer for sale, import, 
 *     and/or otherwise dispose of its contribution in the software or derivative works of the 
 *     contribution in the software.
 * 
 * 3. Conditions and Limitations
 * (A) No Trademark License- This license does not grant you rights to use any contributors' 
 *     name, logo, or trademarks.
 * (B) If you bring a patent claim against any contributor over patents that you claim are 
 *     infringed by the software, your patent license from such contributor to the software 
 *     ends automatically.
 * (C) If you distribute any portion of the software, you must retain all copyright, patent, 
 *     trademark, and attribution notices that are present in the software.
 * (D) If you distribute any portion of the software in source code form, you may do so only under 
 *     this license by including a complete copy of this license with your distribution. If you 
 *     distribute any portion of the software in compiled or object code form, you may only do so 
 *     under a license that complies with this license.
 * (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no 
 *     express warranties, guarantees or conditions. You may have additional consumer rights under 
 *     your local laws which this license cannot change. To the extent permitted under your local laws, 
 *     the contributors exclude the implied warranties of merchantability, fitness for a particular 
 *     purpose and non-infringement.
 * 
 */
using System;
using System.Diagnostics;
using System.Reflection;
using System.Reflection.Emit;

namespace NPack.Numeric
{
    internal static class DynamicGenericMethodGenerator
    {
        public static Func<TLeft, TRight, TResult> GenerateNumericOperation<TLeft, TRight, TResult, TOwner>(
            Operator @operator)
        {
            string methodName;
            OpCode opcode;
            GetParams(@operator, out opcode, out methodName);

            Debug.WriteLine(
                string.Format(
                    @"Creating {4} delegate for:
                TLeft = {0}
                TRight = {1}
                TResult = {2}
                TOwner = {3}",
                    typeof (TLeft),
                    typeof (TRight),
                    typeof (TResult),
                    typeof (TOwner),
                    methodName)
                );


            // create the DynamicMethod
            DynamicMethod method =
                new DynamicMethod(
                    methodName
                    + ":" + typeof (TLeft)
                    + ":" + typeof (TRight)
                    + ":" + typeof (TResult)
                    + ":" + typeof (TOwner),
                    typeof (TLeft),
                    new[]
                        {
                            typeof (TLeft),
                            typeof (TRight)
                        },
                    typeof (TOwner)
                    );

            ILGenerator generator = method.GetILGenerator();

            // generate the opcodes for the method body
            generator.Emit(OpCodes.Ldarg_0);
            generator.Emit(OpCodes.Ldarg_1);

            if (typeof (TLeft).IsPrimitive)
            {
                // if we're working with a primitive, 
                // use the IL Add OpCode
                generator.Emit(opcode);
            }
            else
            {
                // otherwise, bind to the definition 
                // with the given type
                MethodInfo info = typeof (TLeft).GetMethod(
                    methodName,
                    new[]
                        {
                            typeof (TLeft),
                            typeof (TRight)
                        },
                    null
                    );

                generator.EmitCall(OpCodes.Call, info, null);
            }

            // insert a return statement
            generator.Emit(OpCodes.Ret);

            Debug.WriteLine("Method name = " + method.Name);


            // store the delegate for later use
            return (Func<TLeft, TRight, TResult>) method.CreateDelegate(typeof (Func<TLeft, TRight, TResult>));
        }

        private static void GetParams(Operator op, out OpCode opcode, out string methodName)
        {
            switch (op)
            {
                case Operator.Add:
                    {
                        opcode = OpCodes.Add;
                        methodName = "op_Addition";
                        return;
                    }
                case Operator.Subtract:
                    {
                        opcode = OpCodes.Sub;
                        methodName = "op_Subtraction";
                        return;
                    }
                case Operator.Multiply:
                    {
                        opcode = OpCodes.Mul;
                        methodName = "op_Multiplication";
                        return;
                    }
                case Operator.Divide:
                    {
                        opcode = OpCodes.Div;
                        methodName = "op_Division";
                        return;
                    }
            }
            throw new ArgumentException();
        }

        public static Func<TConstructor, TResult> GenerateMethod<TConstructor, TResult>(Operator op)
        {
            throw new NotImplementedException();
             Func<TConstructor, TResult>  c =  o=> (TResult)(object) o;//hmm suspect to do: replace
            return c;
        }
    }
}